package com.ev.hr.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.ev.apis.model.DsResultResponse;
import com.ev.framework.il8n.MessageSourceHandler;
import com.ev.framework.utils.R;
import com.ev.framework.utils.StringUtils;
import com.ev.hr.dao.SalaryAccountingDao;
import com.ev.hr.dao.SalaryFormulaMainDao;
import com.ev.hr.domain.SalaryAccountingDO;
import com.ev.hr.domain.SalaryFormulaChildDO;
import com.ev.hr.domain.SalaryFormulaMainDO;
import com.ev.hr.domain.SalaryProjectDO;
import com.ev.hr.enums.AttendanceProjectDict;
import com.ev.hr.enums.EnumFormulasParameterType;
import com.ev.hr.service.SalaryFormulaChildService;
import com.ev.hr.service.SalaryFormulaMainService;
import com.ev.hr.service.SalaryProjectService;
import com.ev.hr.vo.SalaryFormulaChildDetail;
import com.ev.hr.vo.SalaryFormulaMainPageParam;
import com.ev.hr.vo.SalaryFormulaVO;
import com.google.common.collect.Maps;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;


@Service
public class SalaryFormulaMainServiceImpl implements SalaryFormulaMainService {
    @Autowired
    private SalaryFormulaMainDao salaryFormulaMainDao;
    @Autowired
    private SalaryAccountingDao salaryAccountingDao;
    @Autowired
    private MessageSourceHandler messageSourceHandler;
    @Autowired
    private SalaryProjectService salaryProjectService;
    @Autowired
    private SalaryFormulaChildService salaryFormulaChildService;

    @Override
    public SalaryFormulaMainDO get(Long id) {
        return salaryFormulaMainDao.get(id);
    }

    @Override
    public List<SalaryFormulaMainDO> list(Map<String, Object> map) {
        return salaryFormulaMainDao.list(map);
    }

    @Override
    public int count(Map<String, Object> map) {
        return salaryFormulaMainDao.count(map);
    }

    @Override
    public int save(SalaryFormulaMainDO salaryFormulaMain) {
        return salaryFormulaMainDao.save(salaryFormulaMain);
    }

    @Override
    public int update(SalaryFormulaMainDO salaryFormulaMain) {
        return salaryFormulaMainDao.update(salaryFormulaMain);
    }

    @Override
    public int updateAll(SalaryFormulaMainDO salaryFormulaMain) {
        return salaryFormulaMainDao.updateAll(salaryFormulaMain);
    }

    @Override
    public int remove(Long id) {
        return salaryFormulaMainDao.remove(id);
    }

    @Override
    public int batchRemove(Long[] ids) {
        return salaryFormulaMainDao.batchRemove(ids);
    }

    @Override
    public R saveAndVerify(SalaryFormulaVO saveParam) {
        Long mainId;
        Long id = saveParam.getId();
        //验证名称是否重复
        if (this.checkRepeat(saveParam)) {
            return R.error(messageSourceHandler.getMessage("common.duplicate.names", null));
        }
        JSONObject jObject;
        JSONArray jsonArrayFormula;
        JSONArray jsonArrayFormulaTemp;
        SalaryFormulaMainDO salaryFormulaMainDO = new SalaryFormulaMainDO();
        BeanUtils.copyProperties(saveParam, salaryFormulaMainDO);
        if (id == null) {//新增
            //验证公式是否格式是否正确
            //把公式中的数字重新整理一遍（页面传过来的数字都是分散的，现在把数字整合到一起）
            List<SalaryFormulaChildDO> salaryFormulaChildList = saveParam.getSalaryFormulaChildList();
            for (SalaryFormulaChildDO salaryFormulaChild : salaryFormulaChildList) {
                String formulaJsonarray = salaryFormulaChild.getFormulaJsonarray();
                jsonArrayFormula = JSON.parseArray(formulaJsonarray);
                jsonArrayFormulaTemp = new JSONArray();
                String strPerTypeChange = "";
                String strPerNameChange = "";
                for (int i = 0; i < jsonArrayFormula.size(); i++) {
                    jObject = jsonArrayFormula.getJSONObject(i);
                    String type = jObject.get("type").toString();
                    String text = jObject.get("text").toString();

                    if (type.equals(EnumFormulasParameterType.NUM.getCode())) {
                        strPerNameChange = strPerNameChange + text;
                        if (i == jsonArrayFormula.size() - 1) {
                            JSONObject jObjectTemp = new JSONObject();
                            jObjectTemp.put("type", strPerTypeChange);
                            jObjectTemp.put("id", strPerNameChange);
                            jObjectTemp.put("text", strPerNameChange);
                            jsonArrayFormulaTemp.add(jObjectTemp);
                        }
                    } else {
                        if (strPerTypeChange.equals(EnumFormulasParameterType.NUM.getCode())) {
                            if (strPerNameChange.length() - strPerNameChange.replace(".", "").length() > 1) {
                                String[] args = {strPerNameChange};
                                return R.error(messageSourceHandler.getMessage("hr.formula.content.error", args));
                            }
                            JSONObject jObjectTemp = new JSONObject();
                            jObjectTemp.put("type", strPerTypeChange);
                            jObjectTemp.put("id", strPerNameChange);
                            jObjectTemp.put("text", strPerNameChange);
                            jsonArrayFormulaTemp.add(jObjectTemp);
                        }
                        jsonArrayFormulaTemp.add(jObject);
                        strPerNameChange = "";
                    }
                    strPerTypeChange = type;
                }
                salaryFormulaChild.setFormulaJsonarray(jsonArrayFormulaTemp.toString());
                Pair<Boolean, String> jsonObject = verifyFormula(salaryFormulaChild);

                if (!jsonObject.getLeft()) {
                    SalaryProjectDO salaryProject = salaryProjectService.get(salaryFormulaChild.getProjectId());
                    return R.error(salaryProject.getSalaryItemName() + ":" + jsonObject.getRight());
                }
            }
            this.salaryFormulaMainDao.save(salaryFormulaMainDO);
            mainId = salaryFormulaMainDO.getId();
        } else {//编辑
            SalaryFormulaMainDO salaryFormulaMainTemp;
            salaryFormulaMainTemp = this.get(saveParam.getId());
            if (salaryFormulaMainTemp == null) {
                return R.error(messageSourceHandler.getMessage("hr.formula.content.notExist", null));
            }
            //验证公式是否格式是否正确
            for (SalaryFormulaChildDO salaryFormulaChild : saveParam.getSalaryFormulaChildList()) {
                //验证公式不能包含本身的薪资项目
                //把公式中的数字重新整理一遍（页面传过来的数字都是分散的，现在把数字整合到一起）
                String formulaJsonarray = salaryFormulaChild.getFormulaJsonarray();
                jsonArrayFormula = JSON.parseArray(formulaJsonarray);
                jsonArrayFormulaTemp = new JSONArray();
                String strPerTypeChange = "";
                String strPerNameChange = "";
                for (int i = 0; i < jsonArrayFormula.size(); i++) {
                    jObject = jsonArrayFormula.getJSONObject(i);
                    String type = jObject.get("type").toString();
                    String text = jObject.get("text").toString();
                    if (type.equals(EnumFormulasParameterType.NUM.getCode())) {
                        strPerNameChange = strPerNameChange + text;
                        if (i == jsonArrayFormula.size() - 1) {
                            JSONObject jObjectTemp = new JSONObject();
                            jObjectTemp.put("type", strPerTypeChange);
                            jObjectTemp.put("id", strPerNameChange);
                            jObjectTemp.put("text", strPerNameChange);
                            jsonArrayFormulaTemp.add(jObjectTemp);
                        }
                    } else {
                        if (strPerTypeChange.equals(EnumFormulasParameterType.NUM.getCode())) {
                            if (strPerNameChange.length() - strPerNameChange.replace(".", "").length() > 1) {
                                String[] args = {strPerNameChange};
                                return R.error(messageSourceHandler.getMessage("hr.formula.content.error", args));
                            }
                            JSONObject jObjectTemp = new JSONObject();
                            jObjectTemp.put("type", strPerTypeChange);
                            jObjectTemp.put("id", strPerNameChange);
                            jObjectTemp.put("text", strPerNameChange);
                            jsonArrayFormulaTemp.add(jObjectTemp);
                        }
                        jsonArrayFormulaTemp.add(jObject);
                        strPerNameChange = "";
                    }
                    strPerTypeChange = type;
                }
                //验证计算公式是否正确
                salaryFormulaChild.setFormulaJsonarray(jsonArrayFormulaTemp.toString());
                Pair<Boolean, String> jsonObject = verifyFormula(salaryFormulaChild);
                if (!jsonObject.getLeft()) {
                    SalaryProjectDO salaryProject = salaryProjectService.get(salaryFormulaChild.getProjectId());
                    return R.error(salaryProject.getSalaryItemName() + ":" + jsonObject.getRight());
                }
            }
            this.update(salaryFormulaMainDO);
            mainId = salaryFormulaMainDO.getId();
            //删除此方案对应的所有计算公式
            salaryFormulaChildService.removeByMainId(mainId);
        }
        //计算公式计算顺序(A项目对应的公式A中使用到B项目的公式B，那么B项目就必须先计算，所以对应公式B的排序就应该在公式A前面)
        //循环方案中的所有公式，获取公式调用到的所有计算的项目
        List<SalaryFormulaChildDetail> salaryFormulaChildDetailList = new ArrayList<>();
        //获取所有需要计算的薪资项目id
        List<SalaryFormulaChildDO> salaryFormulaChildList = saveParam.getSalaryFormulaChildList();
        List<Long> projectIds = salaryFormulaChildList
                .stream()
                .map(SalaryFormulaChildDO::getProjectId)
                .collect(Collectors.toList());

        // 找出所有薪资项目
        Map<String, Object> params = Maps.newHashMap();
        List<SalaryProjectDO> salaryProjectDOS = salaryProjectService.list(params);
        //  value  对应 id的map
        Map<String, Long> projectValueToId = salaryProjectDOS
                .stream()
                .collect(Collectors.toMap( SalaryProjectDO::getValue,SalaryProjectDO::getId));
        List<String> listValue = AttendanceProjectDict.getListValue();

        for (SalaryFormulaChildDO salaryFormulaChild : salaryFormulaChildList) {
            SalaryFormulaChildDetail salaryFormulaChildDetail = new SalaryFormulaChildDetail();
            BeanUtils.copyProperties(salaryFormulaChild, salaryFormulaChildDetail);
            String formulaJsonArray = salaryFormulaChild.getFormulaJsonarray();
            jsonArrayFormula = JSON.parseArray(formulaJsonArray);
            List<String> values = new ArrayList<>();
            for (Object object : jsonArrayFormula) {
                JSONObject jsonObject = (JSONObject) object;
                String value = jsonObject.getString("id");
                //获取包含的子项是需要计算的薪资项目的id集合
                if (jsonObject.getString("type").equals(EnumFormulasParameterType.SALARY_PROJECT.getCode())
                        // 考勤项目不记录
                        && !listValue.contains(value)
                        && projectIds.contains(projectValueToId.get(value))
                        ) {
                    values.add(jsonObject.getString("id"));
                }
            }
            salaryFormulaChildDetail.setValueList(values);
            salaryFormulaChildDetailList.add(salaryFormulaChildDetail);
        }


        for (SalaryFormulaChildDetail salaryFormulaChildDetail : salaryFormulaChildDetailList) {
            Integer tempLevel = getFormulaLevel(1, salaryFormulaChildDetail, salaryFormulaChildDetailList,projectValueToId);// 获取计算等级
            salaryFormulaChildDetail.setLevel(tempLevel);// 这个等级在下面几行中就根据等级对收费项目进行排序。
        }
        //添加公式
        for (SalaryFormulaChildDetail salaryFormulaChildDetail : salaryFormulaChildDetailList) {
            SalaryFormulaChildDO salaryFormulaChild = new SalaryFormulaChildDO();
            BeanUtils.copyProperties(salaryFormulaChildDetail, salaryFormulaChild);
            salaryFormulaChild.setMainId(mainId);
            salaryFormulaChildService.save(salaryFormulaChild);
        }
        Map<String, Object> result = Maps.newHashMap();
        result.put("id", mainId);
        return R.ok(result);
    }

    private boolean checkRepeat(SalaryFormulaVO saveParam) {
        return salaryFormulaMainDao.checkRepeat(saveParam) > 0;
    }

    public Pair<Boolean, String> verifyFormula(SalaryFormulaChildDO salaryFormulaChild) {
        String info = salaryFormulaChild.getFormulaJsonarray();
        String formulaTemp = salaryFormulaChild.getFormulaStr();
        JSONArray jsonArrayFormula = JSON.parseArray(info);
        String strPerType = "";
        String strPerName = "";
        List<String> basicSymbolList = new ArrayList<>();
        basicSymbolList.add("+");
        basicSymbolList.add("-");
        basicSymbolList.add("*");
        basicSymbolList.add("/");
        List<String> verifyBracketList = new ArrayList<>();
        if (jsonArrayFormula == null || jsonArrayFormula.size() == 0) {
            return Pair.of(false, messageSourceHandler.getMessage("hr.formula.content.isEmpty", null));
        }
        JSONObject jbject;
        for (int i = 0; i < jsonArrayFormula.size(); i++) {
            jbject = jsonArrayFormula.getJSONObject(i);
            String type = jbject.getString("type");
            String text = jbject.getString("text");
            String id = jbject.getString("id");
            //判断基本运算符+-*/为2目运算符所以最后一位不应该是运算符
            if (i == jsonArrayFormula.size() - 1 && basicSymbolList.contains(text)) {
                String[] args = {strPerName + text};
                return Pair.of(false, messageSourceHandler.getMessage("hr.formula.content.error", args));
            }

            if (id.equals(salaryFormulaChild.getProjectId().toString())) {
                return Pair.of(false, messageSourceHandler.getMessage("hr.formula.content.containsItself", null));
            }
            switch (text) {
                case "[":
                case "(":
                    verifyBracketList.add(text);
                    break;
                case "]":
                    if (verifyBracketList.size() == 0 || !verifyBracketList.get(verifyBracketList.size() - 1).equals("[")) {
                        return Pair.of(false, messageSourceHandler.getMessage("hr.formula.content.sortError", null));
                    }
                    if (verifyBracketList.get(verifyBracketList.size() - 1).equals("[")) {
                        verifyBracketList.remove(verifyBracketList.size() - 1);
                    }
                    break;
                case ")":
                    if (verifyBracketList.size() == 0 || !verifyBracketList.get(verifyBracketList.size() - 1).equals("(")) {
                        return Pair.of(false, messageSourceHandler.getMessage("hr.formula.content.sortError", null));
                    }
                    if (verifyBracketList.get(verifyBracketList.size() - 1).equals("(")) {
                        verifyBracketList.remove(verifyBracketList.size() - 1);
                    }
                    break;
            }
            if (strPerType.equals(EnumFormulasParameterType.NUM.getCode()) || strPerType.equals(EnumFormulasParameterType.SALARY_PROJECT.getCode())) {
                //判断数字、薪资项目（最终都将转化为数字）的数据与括号不能相连
                if (type.equals(EnumFormulasParameterType.NUM.getCode()) || type.equals(EnumFormulasParameterType.SALARY_PROJECT.getCode()) || text.equals("(") || text.equals("[")) {
                    String[] args = {strPerName + text};
                    return Pair.of(false, messageSourceHandler.getMessage("hr.formula.content.error", args));
                }
                //判断数字、因子、收费项目、工单字段类型（最终都将转化为数字）的数据后面不可谓【、（
            }
            //判断基本运算符+-*/不能相连
            if (basicSymbolList.contains(text) && strPerType.equals(type)) {
                String[] args = {strPerName + text};
                return Pair.of(false, messageSourceHandler.getMessage("hr.formula.content.error", args));

            }
            //判断计算公式中不可存在”()、(]、[)、[]、](、)[“
            if (text.equals(")") || text.equals("]")) {
                if (strPerName.equals("(") || strPerName.equals("[")) {
                    String[] args = {strPerName + text};
                    return Pair.of(false, messageSourceHandler.getMessage("hr.formula.content.error", args));

                }
            }
            //判断计算公式中不可存在”](、)[、][、)(“
            if (text.equals("[") || text.equals("(")) {
                if (strPerName.equals(")") || strPerName.equals("]")) {
                    String[] args = {strPerName + text};
                    return Pair.of(false, messageSourceHandler.getMessage("hr.formula.content.error", args));
                }
            }
            //左括号后不能为基本运算符
            if (strPerName.equals("[") || strPerName.equals("(")) {
                if (basicSymbolList.contains(text)) {
                    String[] args = {strPerName + text};
                    return Pair.of(false, messageSourceHandler.getMessage("hr.formula.content.error", args));
                }
            }
            //右括号前不可以跟基本运算符
            if (text.equals(")") || text.equals("]")) {
                if (basicSymbolList.contains(strPerName)) {
                    String[] args = {strPerName + text};
                    return Pair.of(false, messageSourceHandler.getMessage("hr.formula.content.error", args));
                }
            }
            //可转化为数字的数据前不可为】、）
            if (type.equals(EnumFormulasParameterType.NUM.getCode()) || type.equals(EnumFormulasParameterType.SALARY_PROJECT.getCode())) {
                if (strPerName.equals(")") || strPerName.equals("]")) {
                    String[] args = {strPerName + text};
                    return Pair.of(false, messageSourceHandler.getMessage("hr.formula.content.error", args));
                }
            }
            //计算公式其他判断公式是否错误有下面的计算方法来判断
            strPerType = type;
            strPerName = text;
        }
        //验证计算公式中括号是否成对
        char[] chars = formulaTemp.toCharArray();
        int num1 = 0;//记录左小括号的个数（
        int num2 = 0;//记录右小括号的个数）
        int num3 = 0;//记录左中括号的个数【
        int num4 = 0;//记录右中括号的个数】
        for (char aChar : chars) {
            switch (String.valueOf(aChar)) {
                case "(":
                    num1++;
                    break;
                case ")":
                    num2++;
                    break;
                case "[":
                    num3++;
                    break;
                case "]":
                    num4++;
                    break;
            }
        }
        if (num1 != num2) {
            return Pair.of(false, messageSourceHandler.getMessage("hr.formula.content.bigBracketsError", null));
        }
        if (num3 != num4) {
            return Pair.of(false, messageSourceHandler.getMessage("hr.formula.content.middleBracketsError", null));
        }
        return Pair.of(true, null);
    }

    /*
     * 是获取公式计算顺序，处理逻辑是：
     * 计算公式A包含了B、C,B包含D，C包含E、F，E包含G那么公式A的等级为4，其实组成一个树形结构就可以看出来，就是这棵树有几个等级
     * 这个等级是为了计算顺序，因为最底层（等级最小）的公式必须先计算。
     */
    public int getFormulaLevel(int level, SalaryFormulaChildDetail salaryFormulaChild, List<SalaryFormulaChildDetail> salaryFormulaChildList,Map<String,Long> projectValueToId) {
        List<String> valueList = salaryFormulaChild.getValueList();
        if (valueList != null && valueList.size() != 0) {
            for (String value : valueList) {
                SalaryFormulaChildDetail salaryFormulaChildTemp = new SalaryFormulaChildDetail();
                for (SalaryFormulaChildDetail salaryFormulaChildDetail : salaryFormulaChildList) {
                    if (Objects.equals(projectValueToId.get(value),salaryFormulaChildDetail.getProjectId())) {
                        salaryFormulaChildTemp = salaryFormulaChildDetail;
                        break;
                    }
                }
                int tempLevel = this.getFormulaLevel(level + 1, salaryFormulaChildTemp, salaryFormulaChildList,projectValueToId);
                if (tempLevel > level) {
                    level = tempLevel;
                }
            }
        }
        return level;
    }

    @Override
    public Map<String, Object> pageList(SalaryFormulaMainPageParam pageParam) {
        Map<String, Object> result = Maps.newHashMap();
        pageParam.setName(StringUtils.sqlLike(pageParam.getName()));
        List<Map<String, Object>> data = salaryFormulaMainDao.pageList(pageParam);
        int total = salaryFormulaMainDao.pageCount(pageParam);
        result.put("data", new DsResultResponse(pageParam.getPageno(), pageParam.getPagesize(), total, data));
        return R.ok(result);
    }

    @Override
    public Map<String, Object> getById(Long id) {
        Map<String, Object> result = Maps.newHashMap();
        SalaryFormulaMainDO main = salaryFormulaMainDao.get(id);
        if (main != null) {
            Map<String, Object> param = Maps.newHashMap();
            List<SalaryProjectDO> projectList = salaryProjectService.list(param);
            Map<String, String> projectNameMap = projectList.stream()
                    .collect(Collectors.toMap(e -> e.getId().toString(), SalaryProjectDO::getSalaryItemName));

            param.put("mainId", id);
            List<Map<String, Object>> childDOS = this.salaryFormulaChildService.listForMap(param);
            JSONArray jsonArrayFormulaTemp;
            JSONObject jbject;
            JSONArray jsonArrayFormula;
            for (Map<String, Object> child : childDOS) {
                String formulaTemp = child.get("formulaJsonarray").toString();
                jsonArrayFormulaTemp = JSON.parseArray(formulaTemp);
                jsonArrayFormula = new JSONArray();
                for (int i = 0; i < jsonArrayFormulaTemp.size(); i++) {
                    jbject = jsonArrayFormulaTemp.getJSONObject(i);
                    String type = jbject.get("type").toString();
                    String text = jbject.get("text").toString();
                    if (type.equals(EnumFormulasParameterType.NUM.getCode())) {
                        for (int t = 0; t < text.length(); t++) {
                            JSONObject jbjectTemp = new JSONObject();
                            jbjectTemp.put("type", type);
                            jbjectTemp.put("text", text.charAt(t));
                            jbjectTemp.put("id", text.charAt(t));
                            jsonArrayFormula.add(jbjectTemp);
                        }
                    } else {
                        jsonArrayFormula.add(jbject);
                    }
                }
                child.put("formulaJsonarray", jsonArrayFormula.toString());

                child.put("projectName", projectNameMap.get(child.get("projectId").toString()));
            }

            result.put("main", main);
            childDOS.sort(Comparator.comparing(e -> Integer.parseInt(e.getOrDefault("level", 0).toString())));
            result.put("child", childDOS);
        }
        return result;
    }

    @Override
    public R removeByIds(List<Long> ids) {
        //  删除验证 薪资核算中使用则不能删除
        Map<String,Object> param = Maps.newHashMap();
        param.put("formulaIdList",ids);
        List<SalaryAccountingDO> list = salaryAccountingDao.list(param);
        if (list.size() > 0) {
            return R.error(messageSourceHandler.getMessage("hr.salary.formula.isUse", null));
        }

        this.batchRemove(ids.toArray(new Long[0]));
        for (Long id : ids) {
            salaryFormulaChildService.removeByMainId(id);
        }
        return R.ok();
    }

}
